import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import BPNN
from sklearn import metrics
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn import preprocessing

class ANNandDE:
    def __init__(self, file):
        self.file = file
        self.min_max_scaler = None
        self.bp = None
        self.scaler = None
        self.x_train, self.y_train, self.x_test, self.y_test = None, None, None, None
        self.y_pre = None
        self.trace = []

    @staticmethod
    def inverse_transform_col(scaler, y, n_col):
        '''反向转换归一化'''
        y = y.copy()
        y -= scaler.min_[n_col]
        y /= scaler.scale_[n_col]
        return y

    def bpnn_train_predict(self):
        '''使用BPNN进行训练并预测'''
        df1 = pd.read_excel(self.file, 0)
        df1 = df1.iloc[:, :]

        self.min_max_scaler = preprocessing.MinMaxScaler()
        df0 = self.min_max_scaler.fit_transform(df1)

        df = pd.DataFrame(df0, columns=df1.columns)
        x = df.iloc[:, :-1]
        y = df.iloc[:, -1]

        cut = 100  # 取最后cut=100为测试集
        self.x_train, self.x_test = x.iloc[1:80, 0:5].values, x.iloc[80:100, 0:5].values
        self.y_train, self.y_test = y.iloc[1:80].values, y.iloc[80:100].values

        self.bp = BPNN.BPNNRegression([5, 16, 1])
        train_data = [[sx.reshape(5, 1), sy.reshape(1, 1)] for sx, sy in zip(self.x_train, self.y_train)]
        test_data = [np.reshape(sx, (5, 1)) for sx in self.x_test]

        self.bp.MSGD(train_data, 1500, len(train_data), 0.2)

        y_predict = self.bp.predict(test_data)
        self.y_pre = np.array(y_predict).reshape(20)
        self.y_pre = self.inverse_transform_col(self.min_max_scaler, self.y_pre, n_col=5)
        self.y_test = self.inverse_transform_col(self.min_max_scaler, self.y_test, n_col=5)

    def differential_evolution(self):
        '''差分进化算法'''
        NP, D, G = 20, 5, 200
        F0, CR = 0.4, 0.1
        Xs, Xx = np.array([1, 40, 1.4, 90, 30]), np.array([0.4, 20, 1, 50, 12])
        ob, ob1 = np.zeros(NP), np.zeros(NP)

        x = np.zeros((NP, D))
        for i in range(NP):
            x[i, :] = np.random.uniform(Xx, Xs, D)

        self.scaler = preprocessing.MinMaxScaler()
        self.scaler.fit(x)

        v, u = np.zeros((NP, D)), np.zeros((NP, D))

        for i in range(NP):
            ob[i] = self.predict_func(x[i, :])
        self.trace.append(np.min(ob))

        for gen in range(G):
            lambda1 = np.exp(1 - G / (G + 1 - gen))
            F = F0 * np.power(2, lambda1)

            for m in range(NP):
                r1 = self.random_int(m)
                r2 = self.random_int(m, r1)
                r3 = self.random_int(m, r1, r2)

                v[m, :] = x[r1, :] + F * (x[r2, :] - x[r3, :])

                r = np.random.randint(0, D, 1)
                for n in range(D):
                    cr = np.random.random()
                    if (cr < CR) or (n == r):
                        u[:, n] = v[:, n]
                    else:
                        u[:, n] = x[:, n]

                for n in range(D):
                    if u[m, n] < Xx[n] or u[m, n] > Xs[n]:
                        u[m, n] = np.random.uniform(Xx[n], Xs[n])

            for m in range(NP):
                ob1[m] = self.predict_func(u[m, :])

            for m in range(NP):
                if ob1[m] > ob[m]:
                    x[m, :] = u[m, :]

            for m in range(NP):
                ob[m] = self.predict_func(x[m, :])

            self.trace.append(np.min(ob))

        index = np.argmin(ob)
        print('最优值解\n', x[index, :])
        print('最优值\n', self.predict_func(x[index, :]))

    def predict_func(self, x):
        x = x.reshape(1, -1)
        x_normalized = self.scaler.transform(x)
        x1 = [np.reshape(sx, (5, 1)) for sx in x_normalized]
        y_output = self.bp.predict(x1)
        y_output = self.inverse_transform_col(self.min_max_scaler, y_output, n_col=5)
        return y_output

    def random_int(self, m, r1=None, r2=None):
        r = np.random.randint(0, 20, 1)
        while(r == m) or (r == r1) or (r == r2):
            r = np.random.randint(0, 20, 1)
        return r

    def show_predict_plot(self):
        '''展示预测结果的曲线图'''
        draw = pd.concat([pd.DataFrame(self.y_test), pd.DataFrame(self.y_pre)], axis=1)
        draw.iloc[:, 0].plot(figsize=(12, 6))
        draw.iloc[:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), loc='upper right', fontsize='15')
        plt.title("Test Data", fontsize='30')
        plt.show()

    def get_metrics(self):
        '''获取并打印精度指标'''
        print('MAE:', mean_absolute_error(self.y_pre, self.y_test))
        print('MSE:', mean_squared_error(self.y_pre, self.y_test))
        mape = np.mean(np.abs((self.y_pre - self.y_test) / (self.y_test))) * 100
        print('MAPE:', mape, '%')
        print("R2:", metrics.r2_score(self.y_test, self.y_pre))

    def diff_evolution_plot(self):
        plt.plot(self.trace)
        plt.title('Iterative Curve')
        plt.show()

    def run(self):
        self.bpnn_train_predict()
        self.differential_evolution()
        self.show_predict_plot()
        self.get_metrics()
        self.diff_evolution_plot()

if __name__ == "__main__":
    ann_de = ANNandDE('DEBPdate.xlsx')
    ann_de.run()